package com.northpool.operator.analysis.proximity;

import cn.hutool.core.stream.StreamUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.northpool.operator.annotation.InputParam;
import com.northpool.operator.annotation.InputParams;
import com.northpool.operator.annotation.OperatorInfo;
import com.northpool.operator.common.algorithm.OperatorTestBuild;
import com.northpool.operator.common.algorithm.TestParamBuilder;
import com.northpool.operator.common.database.DatabaseUtils;
import com.northpool.operator.exception.ParamException;
import com.northpool.operator.param.ParamTool;
import com.northpool.operator.param.custom.DatasetParam;
import com.northpool.operator.type.TypeEnum;
import com.northpool.operator.utils.StpOperatorHelper;
import com.northpool.resources.command.QueryFilter;
import com.northpool.resources.datasource.IDataSource;
import com.northpool.resources.datasource.db.DbDataSource;
import com.northpool.resources.datatable.FieldEncoder;
import com.northpool.resources.datatable.ITable;
import com.northpool.resources.datatable.SpatialInfo;
import com.northpool.resources.datatable.dao.IMapDAO;
import com.northpool.resources.datatable.operate.ColumnBean;
import com.northpool.resources.datatable.operate.TableSchemaBean;
import com.northpool.spatial.Constants;
import com.northpool.spatial.Geom;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.triangulate.VoronoiDiagramBuilder;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@OperatorInfo(
        ref = "BuildThiessenPolygonOperator",
        name = "构建泰森多边形",
        version = "1.0.0-dataset",
        description = "按照点数据集生成泰森多边形"
)
public class BuildThiessenPolygonOperator extends StpOperatorHelper {

    @InputParams({
            @InputParam(ref = "dataset", name = "数据集", type = TypeEnum.DATASET, desc = "点数据集", required = true),
            @InputParam(ref = "fields", name = "保留字段", desc = "保留到结果数据中的字段,如: code,class", required = false),
            @InputParam(ref = "dataSourceId", name = "输出数据源", desc = "输出数据源ID,如: testkjk", required = true),
            @InputParam(ref = "targetTable", name = "目标数据表名称", desc = "输出到数据表,如: public.result_thiessen", required = true),
            @InputParam(ref = "expandRate", name = "外扩比率", desc = "数据四至外扩的百分比,如: 1 代表外扩数据四至范围的1%", type = TypeEnum.NUMBER, value = "1")
    })
    public BuildThiessenPolygonOperator() {
    }

    public static void main(String[] args) {
        OperatorTestBuild.execute(BuildThiessenPolygonOperator.class,
                TestParamBuilder.aBuilder()
                        .param("dataset", "{\"dataSourceId\":\"kjklz\",\"tableName\":\"public.bj_places\"}")
                        .param("dataSourceId", "kjklz")
                        .param("targetTable", "result.result_thiessen_1")
                        .build());
    }

    @Override
    public void execute(ParamTool paramTool) throws Exception {
        DatasetParam dataset = paramTool.getDataset("dataset");
        String dataSourceId = paramTool.getStr("dataSourceId");
        double expandRate = paramTool.getNumber("expandRate").multiply(BigDecimal.valueOf(0.01)).doubleValue();
        DbDataSource targetDs = (DbDataSource) IDataSource.fromJSON(DatabaseUtils.getDataSourceJson(dataSourceId));

        String targetTable = paramTool.getStr("targetTable");
        if (targetDs.hasTable(targetTable)) {
            throw new ParamException("targetTable", "目标数据表已存在");
        }

        String fields = paramTool.getStr("fields");
        String[] fieldArr = StrUtil.isBlank(fields) ? null : fields.split(",");

        String dataSourceJson = DatabaseUtils.getDataSourceJson(dataset.getDataSourceId());
        IDataSource ds = IDataSource.fromJSON(dataSourceJson);
        ITable table = ds.getTable(dataset.getTableName(), fieldArr, FieldEncoder.ORIGIN_FIELD_ENCODER);
        SpatialInfo spatialInfo = table.spatialInfo();
        String originFieldName = spatialInfo.getOriginFieldName();
        if (!spatialInfo.getGeometryType().name().contains(Constants.GEO_TYPE.POINT.name())) {
            throw new ParamException("dataset", "数据集空间字段类型不是点类型");
        }
        IMapDAO<Object> dao = table.mapDao();
        String[] tableFields = table.getFields();
        if (fieldArr == null) {
            fieldArr = tableFields;
        } else {
            fieldArr = StreamUtil.of(fields).filter(field -> ArrayUtil.contains(tableFields, field)).toArray(String[]::new);
        }
        int spatialFieldIndex = ArrayUtil.indexOf(fieldArr, originFieldName);
        QueryFilter queryFilter = new QueryFilter();
        queryFilter.setOutputFieldNames(fieldArr);
        List<Object[]> dataList = dao.queryArray(queryFilter, 1000);

        VoronoiDiagramBuilder voronoiDiagramBuilder = new VoronoiDiagramBuilder();
        List<Coordinate> coords = new ArrayList<Coordinate>();
        Envelope clipEnvelpoe = new Envelope();
        for (int i = 0; i < dataList.size(); i++) {
            Object[] item = dataList.get(i);
            Geom geom = ArrayUtil.get(item, spatialFieldIndex);
            Geometry jtsGeometry = geom.toJTSGeometry();
            for (int numGeometries = jtsGeometry.getNumGeometries(); numGeometries > 0; numGeometries--) {
                Geometry geometryN = jtsGeometry.getGeometryN(numGeometries);
                Coordinate[] coordinates = geometryN.getCoordinates();
                for (Coordinate coord : coordinates) {
                    coord.z = i; // z存储当前第几个元素
                    coords.add(coord);
                    clipEnvelpoe.expandToInclude(coord);
                    // this.log("POINT({} {})", coord.x, coord.y);
                }
            }
        }
        // clipEnvelpoe 长和宽加上百分之十
        clipEnvelpoe.expandBy(clipEnvelpoe.getWidth() * expandRate, clipEnvelpoe.getHeight() * expandRate);
        voronoiDiagramBuilder.setSites(coords);
        voronoiDiagramBuilder.setClipEnvelope(clipEnvelpoe);
        Geometry geom = voronoiDiagramBuilder.getDiagram(JTSFactoryFinder.getGeometryFactory());

        List<Object[]> resultList = new ArrayList<Object[]>();
        for (int i = 0; i < geom.getNumGeometries(); i++) {
            //下面这个输出语句很重要，用来找到这个多边形对应的原始点位坐标
//            System.out.println(geom.getGeometryN(i).getUserData().hashCode());
            Geometry geometryN = geom.getGeometryN(i);
            Coordinate coord = (Coordinate) geometryN.getUserData();
            Object[] objects = dataList.get((int) coord.z);
            Object[] cloneObj = ObjectUtil.clone(objects);
            Geom newGeom = new Geom(Constants.SPATIAL_TYPE.jtsGeometry, geometryN);
            cloneObj = ArrayUtil.setOrAppend(cloneObj, spatialFieldIndex, newGeom);
            resultList.add(cloneObj);
        }

        TableSchemaBean tableSchemaBean = table.getTableOperator().toTableSchemaBean();
        tableSchemaBean.setSchema(StrUtil.blankToDefault(StrUtil.subBefore(targetTable, ".", true), "public"));
        tableSchemaBean.setTableName(StrUtil.subAfter(targetTable, ".", true));
        ColumnBean columnBean = tableSchemaBean.spatialColumn();
        columnBean.setGeoType(Constants.GEO_TYPE.POLYGON);
        targetDs.createTableBySchemaBeanWithoutIndex(tableSchemaBean);

        this.log("开始批量插入结果到数据库");
        String sql = String.format("INSERT INTO %s (\"%s\") VALUES (%s)",
                StrUtil.format("\"{}\".\"{}\"", tableSchemaBean.getSchema(), tableSchemaBean.getTableName()),
                String.join("\", \"", fieldArr),  // 字段名大写
                String.join(",", Collections.nCopies(fieldArr.length, "?"))
        );
        targetDs.batchExecuteSql(sql, resultList, table.getTypes(), 100);
        this.log("批量写入结果数据成功");
    }
}
